﻿// -----------------------------
// Copyright © Kodi Studios 2012
// -----------------------------

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Reflection;
using System.Threading;
using System.Windows;
using System.Windows.Documents;
using Kodi.Picturethrill;
using System.Text;

namespace Kodi.PicturethrillUpdate
{
    /// <summary>
    /// Interaction logic for PicturethrillUpdateWindow.xaml
    /// This Application needs to be window
    /// because Console should never pop up during execution
    /// </summary>
    public partial class PicturethrillUpdateWindow : Window
    {
        private const int UpdateTimeout = 1 * 60 * 60 * 1000; // 1 hour, in milliseconds

        // Should be enough for Picturethrill to start, run, close. If task is skipped.
        private const int UpdateWaitTimeDefault = 2 * 60 * 1000; // 2 minutes, in milliseconds

        private int updateWaitTime = UpdateWaitTimeDefault;
        private bool helpRequested;
        private bool runUpdate;
        private bool attachDebugger;

        public PicturethrillUpdateWindow()
        {
            using (Log.Group("PicturethrillUpdateWindow Constructor"))
            {

                try
                {
                    InitializeComponent(); // TODO: is this necessary?

                    // Extract arguments
                    List<string> args = new List<string>();
                    string[] argsProperties = (string[])Application.Current.Properties[PicturethrillUpdateApp.ArgsPropertiesName];
                    if (argsProperties != null)
                    {
                        args.AddRange(argsProperties);
                    }

                    StringBuilder argsString = new StringBuilder();
                    foreach (string arg in args)
                    {
                        argsString.Append(arg + " ");
                    }
                    Log.Comment("CommandLine: " + Process.GetCurrentProcess().ProcessName + " " + argsString.ToString());

                    // Setup command-line options
                    List<CommandLineArgumentFlag> commandLineArgumentFlags = new List<CommandLineArgumentFlag>();
                    commandLineArgumentFlags.Add(new CommandLineArgumentFlag("r", () => { this.runUpdate = true; }));
                    commandLineArgumentFlags.Add(new CommandLineArgumentFlag("w", true, (data) => { updateWaitTime = int.Parse(data); }));
                    commandLineArgumentFlags.Add(new CommandLineArgumentFlag("a", () => { this.attachDebugger = true; }));
                    commandLineArgumentFlags.Add(new CommandLineArgumentFlag("h", () => { this.helpRequested = true; }));
                    commandLineArgumentFlags.Add(new CommandLineArgumentFlag("?", () => { this.helpRequested = true; }));
                    commandLineArgumentFlags.Add(new CommandLineArgumentFlag("url", (s) => { this.providedUrl = s; }));

                    CommandLineArgumentParser.Parse(args, commandLineArgumentFlags);

                    if (this.attachDebugger)
                    {
                        MessageBox.Show("Attach to process " + Process.GetCurrentProcess().ProcessName + "(" + Process.GetCurrentProcess().Id + ")");
                    }

                    if (this.helpRequested)
                    {
                        Console.WriteLine("TODO: Help Stuff");
                    }

                    if (!this.runUpdate)
                    {
                        // Put app in temp folder, so that everything in app folder can be updated.
                        this.PrepareUpdate();
                    }
                    else
                    {
                        // Actually run PicturethrillSetup.exe
                        RunUpdate();
                    }
                }
                catch (Exception e)
                {
                    Log.Comment("Exception caught:");
                    Log.Comment(e.ToString());
                }

                this.Close();
                Log.Comment("Exit " + Process.GetCurrentProcess().ProcessName);
            }
        }

        private void PrepareUpdate()
        {
            Log.Comment("Prepare Update");
            // Copy to %TEMP%
            string executingAssembly = Assembly.GetExecutingAssembly().Location;
            string executingAssemblyTemp = Path.Combine(Settings.TempPath, Path.GetFileName(executingAssembly));

            string executingAssemblyConfig = executingAssembly + ".config";
            string executingAssemblyConfigTemp = Path.Combine(Settings.TempPath, Path.GetFileName(executingAssemblyConfig));

            string dependentAssembly = Path.Combine(Path.GetDirectoryName(executingAssembly), "Picturethrill.exe");
            string dependentAssemblyTemp = Path.Combine(Settings.TempPath, Path.GetFileName(dependentAssembly));

            Log.Comment(string.Format("Copy '{0}' to '{1}'", executingAssembly, executingAssemblyTemp));
            File.Copy(executingAssembly, executingAssemblyTemp, overwrite: true);

            Log.Comment(string.Format("Copy '{0}' to '{1}'", dependentAssembly, dependentAssemblyTemp));
            File.Copy(dependentAssembly, dependentAssemblyTemp, overwrite: true);

            Log.Comment(string.Format("Copy '{0}' to '{1}'", executingAssemblyConfig, executingAssemblyConfigTemp));
            File.Copy(executingAssemblyConfig, executingAssemblyConfigTemp, overwrite: true);

            // Run from %TEMP%
            string executingAssemblyArgs = "-r";

            if (!string.IsNullOrEmpty(this.providedUrl))
            {
                executingAssemblyArgs += " -url " + this.providedUrl;
            }

            if (this.attachDebugger)
            {
                executingAssemblyArgs += " -a";
            }

            Log.Comment(string.Format("Execute '{0}' {1}", executingAssemblyTemp, executingAssemblyArgs));
            Process.Start(executingAssemblyTemp, executingAssemblyArgs);
         
        }

        private bool WaitForProcesses()
        {
            bool picturethrillRunning = true;
            bool multiplePicturethrillUpdateRunning = true;
            DateTime timeoutTime = DateTime.Now.AddMilliseconds(UpdateTimeout);

#if !DEBUG
            // Wait for Picturethrill to start and be running.
            Log.Comment("Sleeping " + updateWaitTime + " milliseconds");
            Thread.Sleep(updateWaitTime);
#endif

            do
            {
                // Check Picturethrill is running
                if (Process.GetProcessesByName("Picturethrill").Length == 0)
                {
                    picturethrillRunning = false;
                }

                if (Process.GetProcessesByName("PicturethrillUpdate").Length == 1)
                {
                    multiplePicturethrillUpdateRunning = false;
                }

                if (!picturethrillRunning && !multiplePicturethrillUpdateRunning)
                {
                    return true;
                }

                Log.Comment("Picturethrill or PicturethrillUpdate is running. Sleeping " + updateWaitTime + " milliseconds.");
                Thread.Sleep(updateWaitTime);
            } while (DateTime.Now < timeoutTime);
            return false;
        }

        private void RunUpdate()
        {
            if (!WaitForProcesses())
            {
                Log.Comment("Timed out while waiting for processes");
                return;
            }

            if (!WaitForNetwork())
            {
                Log.Comment("Timed out while waiting for network");
                return;
            }

            this.RunUpdateCheckVersion();
        }

        private bool WaitForNetwork()
        {
            bool internetConnectionAvailable = false;
            DateTime timeoutTime = DateTime.Now.AddMilliseconds(updateWaitTime);
            do
            {
                if (!NetworkUtilities.InternetConnectionAvailable())
                {
                    Log.Comment("Network connection unavailable. Retry after 10 seconds.");
                    Thread.Sleep(TimeSpan.FromSeconds(10));
                }
                else
                {
                    internetConnectionAvailable = true;
                    break;
                }
            } while (timeoutTime > DateTime.Now);

            if (internetConnectionAvailable)
            {
                Log.Comment("Network connection available");
                return true;
            }
            else
            {
                Log.Comment("Network connection unavailable. Network connection timeout exceeded after 2 minutes. Stop execution.");
                return false;
            }
        }

        string providedUrl;

        /// <summary>
        /// 
        /// </summary>
        private void RunUpdateCheckVersion()
        {
            // Picturethrill could still technically be started.

            // Is Update Available
            Log.Comment("Initialize UpdateService");

            string latestSetupUrl = string.Empty;

            if (!string.IsNullOrEmpty(this.providedUrl))
            {
                latestSetupUrl = this.providedUrl;
            }
            else
            {
                using (UpdateServiceClient picturethrillUpdateService = new UpdateServiceClient())
                {
                    FileVersionInfo picturethrillFileVersionInfo = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location);
                    Version currentPicturethrillVersion = new Version(picturethrillFileVersionInfo.ProductVersion);
                    Log.Comment("Current Version: " + currentPicturethrillVersion.ToString());

                    Version latestPicturethrillVersion = picturethrillUpdateService.GetLatestVersion();
                    Log.Comment("Latest Version: " + latestPicturethrillVersion.ToString());

                    if (currentPicturethrillVersion >= latestPicturethrillVersion)
                    {
                        Log.Comment("Already running Latest Version, no need to update");
                        return;
                    }

                    // Download
                    latestSetupUrl = picturethrillUpdateService.GetSetupUrl(latestPicturethrillVersion);
                }
            }
            Log.Comment("Latest Setup URL: " + latestSetupUrl);

            if (string.IsNullOrEmpty(latestSetupUrl))
            {
                return;
            }


            string latestSetupLocalPath = Path.Combine(Settings.TempPath, Path.GetFileName(latestSetupUrl));

            using (WebClient webClient = new WebClient())
            {
                Log.Comment("Downnload Setup to " + latestSetupLocalPath);
                webClient.DownloadFile(latestSetupUrl, latestSetupLocalPath);
                Log.Comment("Downnload Setup done");
            }


            // check
            // $DESKTOP\Picturethrill.lnk
            // "$SMPROGRAMS\Picturethrill"

            string startMenuPicturethrillPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), "Programs", "Picturethrill");
            string desktopPicturethrillPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "Picturethrill.lnk");

            bool startMenuPicturethrillPathExists = Directory.Exists(startMenuPicturethrillPath);
            bool desktopPicturethrillPathExists = File.Exists(desktopPicturethrillPath);

            this.RunUpdate(latestSetupLocalPath);

            // delete lnk and start programs if they were removed, as we don't want those icons to show up if user deleted them
            if (!startMenuPicturethrillPathExists)
            {
                Log.Comment("Delete " + startMenuPicturethrillPath);
                Directory.Delete(startMenuPicturethrillPath);
            }

            if (!desktopPicturethrillPathExists)
            {
                Log.Comment("Delete " + desktopPicturethrillPath);
                File.Delete(desktopPicturethrillPath);
            }

            // That's it
        }

        private void RunUpdate(string latestSetupLocalPath)
        {
            // Start PicturethrillSetup.exe
            Log.Comment("Start " + latestSetupLocalPath + " /S");
            using (Process setupProcess = Process.Start(latestSetupLocalPath, "/S"))
            {
                setupProcess.WaitForExit();
            }
            Log.Comment("Finished " + latestSetupLocalPath + " /S");
        }
    }

}
